﻿// =============================================
// 引入必要的 Unity 命名空间：
// UnityEngine：基础功能
// UnityEngine.Rendering：提供渲染管线相关 API，如 CommandBuffer、ScriptableRenderContext、CullingResults 等
// =============================================
using UnityEngine;
using UnityEngine.Rendering;

// =============================================
// 定义一个管理定向光源阴影的核心类
// 负责阴影的渲染、资源管理、多光源支持、级联配置等
// =============================================
public class Shadows
{
	// =============================================
	// 常量定义
	// =============================================

	// 命令缓冲区的名字，用于调试和识别
	const string bufferName = "Shadows";

	// 支持的最大定向光源阴影数量 和 最大级联数量
	const int maxShadowedDirLightCount = 4, maxCascades = 4;

	// 定义定向光源可用的 PCF 滤波关键字（用于控制软阴影的滤波方式）
	static string[] directionalFilterKeywords = {
		"_DIRECTIONAL_PCF3",  // 3x3 滤波
        "_DIRECTIONAL_PCF5",  // 5x5 滤波
        "_DIRECTIONAL_PCF7"   // 7x7 滤波
    };

	// 定义级联混合模式的关键字（比如软混合、抖动混合）
	static string[] cascadeBlendKeywords = {
		"_CASCADE_BLEND_SOFT",
		"_CASCADE_BLEND_DITHER"
	};

	// =============================================
	// Shader 属性 ID（用于快速设置全局 Shader 参数）
	// 这些 ID 通过 Shader.PropertyToID 缓存，避免字符串查找开销
	// =============================================
	static int
		dirShadowAtlasId = Shader.PropertyToID("_DirectionalShadowAtlas"),            // 阴影贴图图集
		dirShadowMatricesId = Shader.PropertyToID("_DirectionalShadowMatrices"),      // 阴影变换矩阵数组
		cascadeCountId = Shader.PropertyToID("_CascadeCount"),                        // 级联数量
		cascadeCullingSpheresId = Shader.PropertyToID("_CascadeCullingSpheres"),      // 级联剔除球数组
		cascadeDataId = Shader.PropertyToID("_CascadeData"),                          // 级联数据数组
		shadowAtlasSizeId = Shader.PropertyToID("_ShadowAtlasSize"),                  // 阴影图集尺寸
		shadowDistanceFadeId = Shader.PropertyToID("_ShadowDistanceFade");            // 阴影距离淡出参数

	// =============================================
	// 用于存储级联剔除球和级联数据的临时数组
	// =============================================
	static Vector4[]
		cascadeCullingSpheres = new Vector4[maxCascades],   // 每个级联的球形剔除范围
		cascadeData = new Vector4[maxCascades];             // 每个级联的附加数据（如 Bias、Filter 大小）

	// =============================================
	// 存储所有定向光源的阴影变换矩阵（光源数 × 级联数）
	// =============================================
	static Matrix4x4[]
		dirShadowMatrices = new Matrix4x4[maxShadowedDirLightCount * maxCascades];

	// =============================================
	// 内部数据结构：表示一个可投射阴影的定向光源
	// 包括：其在 VisibleLight 中的索引、斜率比例 Bias、近平面偏移
	// =============================================
	struct ShadowedDirectionalLight
	{
		public int visibleLightIndex;     // 该光源在 CullingResults 可见光源列表中的索引
		public float slopeScaleBias;      // 斜率比例 Bias，用于控制阴影锐利度与伪影
		public float nearPlaneOffset;     // 近平面偏移，避免阴影因近裁剪面被裁掉
	}

	// =============================================
	// 用于存储当前要渲染的定向光源阴影信息（最多 4 个）
	// =============================================
	ShadowedDirectionalLight[] shadowedDirectionalLights =
		new ShadowedDirectionalLight[maxShadowedDirLightCount];

	// 当前实际要渲染的带阴影的定向光源数量
	int shadowedDirLightCount;

	// =============================================
	// CommandBuffer：用于收集所有阴影渲染命令，统一提交
	// =============================================
	CommandBuffer buffer = new CommandBuffer
	{
		name = bufferName
	};

	// =============================================
	// 渲染上下文与可见光源剔除结果
	// =============================================
	ScriptableRenderContext context;
	CullingResults cullingResults;

	// =============================================
	// 用户可配置的阴影设置（通常来自 ShadowSettings.cs 或类似配置类）
	// =============================================
	ShadowSettings settings;

	// =============================================
	// 初始化方法：在每帧渲染前调用，传入渲染上下文、剔除结果和用户设置
	// =============================================
	public void Setup(
		ScriptableRenderContext context, CullingResults cullingResults,
		ShadowSettings settings
	)
	{
		this.context = context;
		this.cullingResults = cullingResults;
		this.settings = settings;
		shadowedDirLightCount = 0; // 重置光源计数
	}

	// =============================================
	// 清理方法：释放临时资源，提交命令缓冲区
	// =============================================
	public void Cleanup()
	{
		buffer.ReleaseTemporaryRT(dirShadowAtlasId); // 释放阴影图集临时 RT
		ExecuteBuffer(); // 提交所有命令
	}

	// =============================================
	// 方法：为某个定向光源预留阴影资源，并返回其配置参数（如强度、索引等）
	// 如果该光源不满足条件（比如无阴影、不可见、无阴影投射体），则返回 Vector3.zero
	// =============================================
	public Vector3 ReserveDirectionalShadows(Light light, int visibleLightIndex)
	{
		if (
			shadowedDirLightCount < maxShadowedDirLightCount && // 不超过最大光源数
			light.shadows != LightShadows.None &&              // 光源开启了阴影
			light.shadowStrength > 0f &&                       // 阴影强度 > 0
			cullingResults.GetShadowCasterBounds(visibleLightIndex, out Bounds b) // 有可投射阴影的物体
		)
		{
			// 记录该光源的索引、Bias、近平面偏移
			shadowedDirectionalLights[shadowedDirLightCount] =
				new ShadowedDirectionalLight
				{
					visibleLightIndex = visibleLightIndex,
					slopeScaleBias = light.shadowBias,
					nearPlaneOffset = light.shadowNearPlane
				};
			// 返回：(shadowStrength, tileOffset, normalBias)
			return new Vector3(
				light.shadowStrength,
				settings.directional.cascadeCount * shadowedDirLightCount++, // tile 偏移量计算
				light.shadowNormalBias
			);
		}
		return Vector3.zero; // 不满足条件，不投射阴影
	}

	// =============================================
	// 方法：执行阴影渲染流程（如果存在需要渲染的光源）
	// =============================================
	public void Render()
	{
		if (shadowedDirLightCount > 0)
		{
			RenderDirectionalShadows(); // 渲染所有定向光源的阴影
		}
		else
		{
			// 如果没有需要渲染的阴影光源，仍创建一个 1x1 的空白阴影图集，避免 Shader 报错
			buffer.GetTemporaryRT(
				dirShadowAtlasId, 1, 1,
				32, FilterMode.Bilinear, RenderTextureFormat.Shadowmap
			);
		}
	}

	// =============================================
	// 方法：实际执行定向光源阴影的渲染逻辑
	// 包括：创建 Shadow Atlas、设置 RenderTarget、分块、逐光源渲染等
	// =============================================
	void RenderDirectionalShadows()
	{
		int atlasSize = (int)settings.directional.atlasSize; // 阴影图集尺寸（比如 1024、2048）
		buffer.GetTemporaryRT(
			dirShadowAtlasId, atlasSize, atlasSize,
			32, FilterMode.Bilinear, RenderTextureFormat.Shadowmap
		);
		// 设置渲染目标为阴影图集，清除为透明
		buffer.SetRenderTarget(
			dirShadowAtlasId,
			RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store
		);
		buffer.ClearRenderTarget(true, false, Color.clear);
		buffer.BeginSample(bufferName); // 性能分析标记
		ExecuteBuffer(); // 提交初始化命令

		// 计算总的阴影瓦片数（光源数 × 级联数），并决定分块策略（1/2/4 分块）
		int tiles = shadowedDirLightCount * settings.directional.cascadeCount;
		int split = tiles <= 1 ? 1 : tiles <= 4 ? 2 : 4; // 分块数：1→1, 2-4→2, 5+→4
		int tileSize = atlasSize / split; // 每块的尺寸

		// 遍历每一个要渲染阴影的光源
		for (int i = 0; i < shadowedDirLightCount; i++)
		{
			RenderDirectionalShadows(i, split, tileSize); // 渲染该光源的每个级联阴影
		}

		// 设置全局 Shader 参数：级联数量、剔除球、级联数据、矩阵数组、淡出参数、图集尺寸等
		buffer.SetGlobalInt(cascadeCountId, settings.directional.cascadeCount);
		buffer.SetGlobalVectorArray(cascadeCullingSpheresId, cascadeCullingSpheres);
		buffer.SetGlobalVectorArray(cascadeDataId, cascadeData);
		buffer.SetGlobalMatrixArray(dirShadowMatricesId, dirShadowMatrices);

		// 计算级联间淡出参数（用于距离混合）
		float f = 1f - settings.directional.cascadeFade;
		buffer.SetGlobalVector(
			shadowDistanceFadeId, new Vector4(
				1f / settings.maxDistance, 1f / settings.distanceFade,
				1f / (1f - f * f)
			)
		);

		// 设置当前使用的 PCF 滤波与级联混合关键字（通过 Shader Feature 控制）
		SetKeywords(directionalFilterKeywords, (int)settings.directional.filter - 1);
		SetKeywords(cascadeBlendKeywords, (int)settings.directional.cascadeBlend - 1);

		// 设置全局阴影图集尺寸的倒数（用于 Shader 中 UV 映射）
		buffer.SetGlobalVector(shadowAtlasSizeId, new Vector4(atlasSize, 1f / atlasSize));

		buffer.EndSample(bufferName); // 结束性能标记
		ExecuteBuffer(); // 提交所有渲染与设置命令
	}

	// =============================================
	// 方法：设置当前使用的 Shader Keyword（比如 _DIRECTIONAL_PCF3、_CASCADE_BLEND_SOFT）
	// enabledIndex: 启用第几个关键字（从 0 开始）
	// =============================================
	void SetKeywords(string[] keywords, int enabledIndex)
	{
		for (int i = 0; i < keywords.Length; i++)
		{
			if (i == enabledIndex)
			{
				buffer.EnableShaderKeyword(keywords[i]); // 启用指定的关键字
			}
			else
			{
				buffer.DisableShaderKeyword(keywords[i]); // 禁用其它关键字
			}
		}
	}

	// =============================================
	// 方法：为某一个光源（索引 i）渲染其所有级联的阴影
	// 包括：计算阴影矩阵、设置视口、执行 DrawShadows 等
	// =============================================
	void RenderDirectionalShadows(int index, int split, int tileSize)
	{
		ShadowedDirectionalLight light = shadowedDirectionalLights[index];
		var shadowSettings = new ShadowDrawingSettings(
			cullingResults, light.visibleLightIndex,
			BatchCullingProjectionType.Orthographic // 定向光源通常使用正交投影
		);

		int cascadeCount = settings.directional.cascadeCount;
		int tileOffset = index * cascadeCount; // 该光源的起始瓦片索引
		Vector3 ratios = settings.directional.CascadeRatios; // 级联划分比例（比如近、中、远）
		float cullingFactor =
			Mathf.Max(0f, 0.8f - settings.directional.cascadeFade); // 级联混合剔除因子

		// 遍历该光源的每一个级联
		for (int i = 0; i < cascadeCount; i++)
		{
			// 计算该级联的视图矩阵、投影矩阵、以及 Split 数据（包含剔除球等）
			cullingResults.ComputeDirectionalShadowMatricesAndCullingPrimitives(
				light.visibleLightIndex, i, cascadeCount, ratios, tileSize,
				light.nearPlaneOffset, out Matrix4x4 viewMatrix,
				out Matrix4x4 projectionMatrix, out ShadowSplitData splitData
			);
			splitData.shadowCascadeBlendCullingFactor = cullingFactor; // 设置混合剔除参数
			shadowSettings.splitData = splitData;

			if (index == 0)
			{
				// 如果是第一个光源，设置该级联的剔除球与数据（用于 Shader 全局参数）
				SetCascadeData(i, splitData.cullingSphere, tileSize);
			}

			int tileIndex = tileOffset + i; // 当前级联在该光源中的瓦片索引
											// 将投影 * 视图矩阵转换为 Atlas 空间中的矩阵，并设置视口
			dirShadowMatrices[tileIndex] = ConvertToAtlasMatrix(
				projectionMatrix * viewMatrix,
				SetTileViewport(tileIndex, split, tileSize),
				split
			);

			// 设置当前渲染的视图投影矩阵
			buffer.SetViewProjectionMatrices(viewMatrix, projectionMatrix);
			// 设置全局 Depth Bias（用于控制阴影锯齿 / Peter Panning）
			buffer.SetGlobalDepthBias(0f, light.slopeScaleBias);
			ExecuteBuffer(); // 提交矩阵与 Bias 设置
							 // 执行实际的阴影渲染（调用 SRP 内部的阴影绘制逻辑）
			context.DrawShadows(ref shadowSettings);
			// 重置 Depth Bias
			buffer.SetGlobalDepthBias(0f, 0f);
		}
	}

	// =============================================
	// 方法：设置当前级联的附加数据（比如用于 Bias 和 Filter 计算）
	// =============================================
	void SetCascadeData(int index, Vector4 cullingSphere, float tileSize)
	{
		float texelSize = 2f * cullingSphere.w / tileSize; // 每个纹素的尺寸
		float filterSize = texelSize * ((float)settings.directional.filter + 1f); // PCF 滤波核大小
		cullingSphere.w -= filterSize; // 调整剔除球半径以避免边缘漏光
		cullingSphere.w *= cullingSphere.w; // 平方（用于距离比较优化）
		cascadeCullingSpheres[index] = cullingSphere; // 保存回全局数组
													  // 保存该级联的 Bias 相关数据
		cascadeData[index] = new Vector4(
			1f / cullingSphere.w,
			filterSize * 1.4142136f // sqrt(2) ？可能用于进一步调整
		);
	}

	// =============================================
	// 方法：将阴影视图投影矩阵转换为阴影图集空间中的矩阵
	// 包括：平移、缩放，以将每个光源的阴影渲染到图集的指定瓦片中
	// =============================================
	Matrix4x4 ConvertToAtlasMatrix(Matrix4x4 m, Vector2 offset, int split)
	{
		if (SystemInfo.usesReversedZBuffer)
		{
			// 如果当前平台使用反向 Z，翻转投影矩阵的 Z 轴分量，确保正确深度比较
			m.m20 = -m.m20;
			m.m21 = -m.m21;
			m.m22 = -m.m22;
			m.m23 = -m.m23;
		}
		float scale = 1f / split; // 每个分块的缩放比例
								  // 应用平移与缩放，将阴影渲染到图集的指定瓦片区域
		m.m00 = (0.5f * (m.m00 + m.m30) + offset.x * m.m30) * scale;
		m.m01 = (0.5f * (m.m01 + m.m31) + offset.x * m.m31) * scale;
		m.m02 = (0.5f * (m.m02 + m.m32) + offset.x * m.m32) * scale;
		m.m03 = (0.5f * (m.m03 + m.m33) + offset.x * m.m33) * scale;
		m.m10 = (0.5f * (m.m10 + m.m30) + offset.y * m.m30) * scale;
		m.m11 = (0.5f * (m.m11 + m.m31) + offset.y * m.m31) * scale;
		m.m12 = (0.5f * (m.m12 + m.m32) + offset.y * m.m32) * scale;
		m.m13 = (0.5f * (m.m13 + m.m33) + offset.y * m.m33) * scale;
		m.m20 = 0.5f * (m.m20 + m.m30);
		m.m21 = 0.5f * (m.m21 + m.m31);
		m.m22 = 0.5f * (m.m22 + m.m32);
		m.m23 = 0.5f * (m.m23 + m.m33);
		return m;
	}

	// =============================================
	// 方法：设置当前光源阴影渲染的视口位置与大小（在 Shadow Atlas 上的瓦片位置）
	// =============================================
	Vector2 SetTileViewport(int index, int split, float tileSize)
	{
		Vector2 offset = new Vector2(index % split, index / split); // 瓦片在图集中的行列位置
		buffer.SetViewport(new Rect(
			offset.x * tileSize, offset.y * tileSize, tileSize, tileSize
		)); // 设置渲染视口为该瓦片区域
		return offset;
	}

	// =============================================
	// 方法：提交当前 CommandBuffer 中所有命令到渲染管线
	// =============================================
	void ExecuteBuffer()
	{
		context.ExecuteCommandBuffer(buffer);
		buffer.Clear(); // 清空命令缓冲区，准备下一轮命令
	}
}